#ifndef AUTHENTICATION_H
#define AUTHENTICATION_H



class authentication : public boost::enable_shared_from_this<authentication>
{
public:
    authentication(boost::asio::io_service& io_service)
        : socket_(io_service), 
		output_deadline_(io_service),
		user_(),
		io_service_(io_service)
    {
    }
	~authentication() 
	{
		cout << "auth session closed for " << remote_.to_string() << endl;
	}
    void start()
    {
		remote_ = socket_.remote_endpoint().address();
		cout << "receive connection from " << remote_.to_string() << endl;

		output_deadline_.expires_from_now(boost::posix_time::seconds(USER_AUTH_TIMEOUT_SECOND));
		// receive hello message
		boost::asio::async_read(socket_, buffer_, boost::asio::transfer_at_least(16),
			boost::bind(&authentication::handle_hello_read, shared_from_this(), 
			boost::asio::placeholders::error));
		// authentication time out
		output_deadline_.async_wait(
			boost::bind(&authentication::check_deadline,
			shared_from_this()));
    }
	void handle_hello_read(const boost::system::error_code& error)
	{
		if (!error)
		{
			c_aes::decrypt(buffer_);
			istream is(&buffer_);
			string msg;
			is >> msg;
			if (msg.find(CLIENT_HELLO_MESSAGE) != string::npos)
			{								
				unsigned char* out = boost::asio::buffer_cast<unsigned char*>(buffer_.prepare(strlen(CLIENT_HELLO_REPLY)));
				memcpy(out, CLIENT_HELLO_REPLY, strlen(CLIENT_HELLO_REPLY));
				buffer_.commit(strlen(CLIENT_HELLO_REPLY));
				c_aes::encrypt(buffer_);

				boost::asio::async_write(socket_,
					buffer_,
					boost::bind(&authentication::handle_hello_write, shared_from_this(),
						boost::asio::placeholders::error));	
			}
			else if (msg.find(CLIENT_KEEPALIVE_TCP) != string::npos)
			{
				cout << "TCP keep alive from: " << remote_.to_string() << endl;
				boost::asio::async_read(socket_, buffer_,//boost::asio::buffer(data_, max_length), 
					boost::asio::transfer_at_least(AES_pad_align_size(sizeof(struct udp_packet_keepalive_client) + MD5_LENGTH)),
					boost::bind(&authentication::handle_keepalive_read, shared_from_this(), 
					boost::asio::placeholders::error));				
			}
			else
			{
				cout << "error client hello message: " << msg << endl;
				cancel_auth();
			}
		}
		else
			cancel_auth();
	}
	void handle_keepalive_read(const boost::system::error_code& error)
	{
		if (!error)
		{
			c_aes::decrypt(buffer_);
			unsigned int sendlen = keep_alive::process_packet( buffer_ );
			if (sendlen)
			{
				c_aes::encrypt(buffer_);
				cout << "send TCP keepalive reply" << endl;
				boost::asio::async_write(socket_,
					buffer_,
					boost::bind( &authentication::cancel_auth, shared_from_this()) );	
			}
			else
				cancel_auth();
		}
		else
			cancel_auth();
	}
	void handle_hello_write(const boost::system::error_code& error)
	{
		cout << "reply hello message" << endl;
		if (!error)
		{
			boost::asio::async_read(socket_, buffer_, boost::asio::transfer_at_least(AES_pad_align_size(sizeof(login_request_client_200))),
				boost::bind(&authentication::handle_user_read, shared_from_this(), 
				boost::asio::placeholders::error));
		}
	}
	void handle_user_read(const boost::system::error_code& error)
	{
		cout << "read user info" << endl;
		if (!error)
		{
			c_aes::decrypt(buffer_);

			const unsigned char *in = boost::asio::buffer_cast<const unsigned char*>(buffer_.data());
			struct login_request_client_200 *client200 = (struct login_request_client_200 *)in;

			printf("%s, mac %s, version %d, type %d, radius version %d\n", 
				client200->username, client200->mac, client200->version, client200->type, client200->radversion);
			
			buffer_.consume(buffer_.size());

			user_ = user_info_ptr(new user_info(client200->type, client200->username,
												client200->password, client200->version, remote_,
												io_service_));
			output_deadline_.cancel();

			rad_callback_t call_back = boost::bind(&authentication::callback_radius_auth_reply, 
										shared_from_this(), _1);
			user_->send_radius_auth(call_back);

		}
	}
	// called from rad_packet::parse_reply
	void callback_radius_auth_reply(rad_packet_ptr &p)        
	{
		if (user_->parse_auth_reply(p) == SERVER_AUTH_REJECT)
			send_user_reply();
		else  // do accounting
		{
			rad_callback_t call_back = boost::bind(&authentication::callback_radius_account_reply, 
											shared_from_this(), _1);		
			user_->send_radius_account(rad_packet::ACCT_START, call_back);
		}
	}
	void callback_radius_account_reply(rad_packet_ptr &p)
	{
		if (user_->parse_account_reply(p) == SERVER_AUTH_REPLY)
			user_->start_account();
		send_user_reply();
	}
	void send_user_reply()
	{
		user_->generate_user_reply(buffer_);
		c_aes::encrypt(buffer_);

		boost::asio::async_write(socket_,
			buffer_,
			boost::bind(&authentication::handle_finish_auth, shared_from_this(),
				boost::asio::placeholders::error));	
	}
	// class this will be deleted after this call
	void handle_finish_auth(const boost::system::error_code& error)
	{
		cout << "auth finish" << endl;		
		
		if (!error)
		{			
		}
		else
		{
		}
	}	
    tcp::socket &get_socket()
    {
        return socket_;
    }

	void check_deadline()
	{
		if (output_deadline_.expires_at() <= deadline_timer::traits_type::now())
		{
			cout << "time out for " << remote_.to_string() << endl;
			// class this will be deleted after this call
			socket_.close();
		}
	}
	void cancel_auth()
	{
		output_deadline_.cancel();
	}
private:	
	tcp::socket socket_;
	deadline_timer output_deadline_;
	user_info_ptr user_;
	boost::asio::io_service& io_service_;
	boost::asio::ip::address remote_;    
	boost::asio::streambuf buffer_;
};

typedef boost::shared_ptr<authentication> authentication_ptr;











#endif